fastai(v2) 라이브러리 설치

아래 두 줄의 명령어의 실행이 완료된 다음, 반드시 런타임을 재시작 해야함

  • 방법: 런타임 메뉴(Runtime) => 런타임 재시작 클릭(Restart runtime)

fastai 버전확인

대부분의 Python 패키지는 __version__ 속성을 가지고 있는데, 이 속성에 접근하면 현재 설치된 패키지의 버전 정보를 확인할 수 있음.

아래 코드의 실행 결과가 2.x.x 가 아니라, 1.x.x 라면, fastai v2의 설치가 정상적으로 되지 않은것임. 따라서, 이전 과정을 다시한번 수행/검토해볼 필요가 있음.

import fastai
print(fastai.__version__)
2.0.7

fastbook 제공, 유틸리티 라이브러리 설치

마이크로소프트 Bing 검색엔진을 통한 이미지 검색 등의 라이브러리는 fastai 차원에서 제공하는 일반화된 라이브러리가 아님. 다만, 책 내용의 실습을 위해서 fastbook 저장소에서 별도로 작성되어 제공되는것임.

만약, 책 실습 외의 상황에서도 search_images_bing 등의 API를 사용하고 싶다면, 반드시 아래 코드를 실행하여 fastbook 패키지를 import 해 줘야함.

fastbook의 유틸리티 함수로서 작성된 함수의 목록은 다음과 같음.

  • search_images_bing
  • plot_function
  • draw_tree
  • cluster_columns

딱히 구현상 어려운 수준의 함수는 아니므로, 참고하여 직접 구현해도 됨.

!pip install -Uqq fastbook
import fastbook

필요한 모든 라이브러리를 import

from fastbook import *
from fastai.vision.all import *
from fastai.vision.widgets import *

Bing 으로부터 이미지 다운로드 및 데이터셋 구축

Azure Cognitive Service API 키 값

API 키를 얻어오기 위해서는

  1. 일단 MS Azure에 가입이 되어 있어야함

    • 계정이 없다면 가입 링크에 접속한 후, "체험 계정 만들기" 버튼을 클릭해서 계정을 생성해야함
    • 계정이 있다면, 단순히 로그인만 해 주면 됨
  2. MS Azure 계정이 있다면, 로그인 후 Azure Portal에 접속해 줘야함

    • 상단의 검색 바에서 "Cognitive Services"를 타이핑한 다음, 검색된 결과를 클릭함 (당연히 Cognitive Services 라는걸 클릭 해야함)
    • +New 버튼을 클릭
    • Marketplace에서, "Bing Search"를 검색하여 클릭
    • "Create" 버튼을 클릭
    • 각종 정보 입력. 단, "Pricing Tier"에는 반드시 "무료인 것을 선택"
      • Resource Group이 없는 경우, 텍스트박스 하단의 "New Group"을 클릭하여 하나 생성
  3. 생성된 Bing Search 를 클릭

    • 좌측 메뉴의 "Keys and Endpoint"를 선택
    • "Show Keys" 버튼을 클릭하여, 숨김표시된 Key 값을 풀어줌
    • Key1 을 복사하여, 아래의 key 값에 넣어줌
key = '당신만의 Azure Cognitive Service (Bing Search) API Key를 넣어주세요'

이미지 다운로드

# 아래 한줄의 코드는 일종의 리스트를 만들어줌 (정확히는 Tuple)
disney_characters = 'disney malificent', 'disney cinderella', 'disney jasmin', 'disney mulan', 'disney belle', 'disney pocahontas'

# Path는 fastai에서 개발한 fastcore에 포함된 기초 라이브러리로,
# 기본적으로는 Python에서 표준적으로 제공하는 pathlib.Path를 확장한 것임
# - pathlib.Path의 기능을 모두 그대로 사용 가능하지만,
# - 여기에 몇 가지 편의 사항을 추가함
#   - .readlines, .read, .write, .save, .load, .ls
path = Path('disney')

# 최상위 디렉토리의 생성
if not path.exists():
  path.mkdir()

# 각 이미지 클래스 별로 반복하여 접근
for character in disney_characters:
  # 클래스 이름의 Path 지정 및 생성
  dest = (path/character)
  dest.mkdir(exist_ok=True)

  # search_images_bing 함수를 이용하여, URL 목록을 가져옴
  # - Azure Cognitive Service API Key 및 검색하고자 하는 키워드를 입력
  # - 이 함수가 반환하는 객체는 Python의 표준 객체인 list를 확장한 L 이라는 객체임 (fastcore)
  results = search_images_bing(key, character)

  # download_images 함수를 이용하여, 준비된 URL 목록의 모든 이미지를 다운로드함
  # - 정확히는 results가 URL 목록은 아니며, Bing Search Service가 반환한 JSON 포맷의 내용임
  # - L 객체는 attrgot 이라는 메서드를 제공하는데, 리스트에 포함된 모든 아이템으로부터 인자로 지정된 속성의 값들만을 추출하여, 별도의 리스트(L)을 반환함
  # - 첫 번째 인자인 dest가 이미지 다운로드 후 저장될 위치임
  download_images(dest, urls=results.attrgot('content_url'))

모든 이미지파일의 Path 목록

fastai에서 제공하는 get_image_files는 지정된 Path를 기점으로, 하위에 포함된 모든 이미지 목록을 재귀적으로 검색하여 들고옴

구분없이 몽땅 들고오는 이유는 다음과 같음

  • 다운로드된 이미지는 폴더이름 단위로 클래스가 구분됨
    • 이후 DataBlock 또는 ImageDataLoaders 객체 생성시 클래스(레이블)을 구분해내기 위한 로직 추가가 가능함. 구분하는 별도의 함수를 만들게 되며, 단순히 규칙을 지정해 주기만 하면됨.

아래 코드의 실행결과는 fnames 객체 내용을 출력해줌

  • 출력 결과의 앞 부분 (#...)은 리스트에 포함된 아이템의 개수를 의미함. 원래 표준 list 객체는 이러한 정보를 출력하지 않으나, L은 출력해 주는 특성이 있음.
fnames = get_image_files(path)
fnames
(#899) [Path('disney/disney mulan/00000005.jpg'),Path('disney/disney mulan/00000040.jpg'),Path('disney/disney mulan/00000122.jpg'),Path('disney/disney mulan/00000104.png'),Path('disney/disney mulan/00000068.jpg'),Path('disney/disney mulan/00000031.jpeg'),Path('disney/disney mulan/00000058.jpg'),Path('disney/disney mulan/00000071.jpg'),Path('disney/disney mulan/00000138.jpg'),Path('disney/disney mulan/00000091.jpg')...]

망가진 링크의 이미지 검사하여, 그 목록을 삭제하기

failed = verify_images(fnames)
failed
(#9) [Path('disney/disney mulan/00000032.jpg'),Path('disney/disney belle/00000128.jpg'),Path('disney/disney belle/00000037.jpg'),Path('disney/disney jasmin/00000123.jpg'),Path('disney/disney jasmin/00000011.png'),Path('disney/disney pocahontas/00000087.jpg'),Path('disney/disney pocahontas/00000019.jpg'),Path('disney/disney malificent/00000098.jpeg'),Path('disney/disney malificent/00000073.jpg')]

L 객체에는 함수형 언어적 기능인 map 메서드가 구현되어 있음. map 메서드의 파라미터는 어떤 함수가 지정될 수 있음.

map 메서드가 호출되는 순간, 각 아이템을 반복적으로 접근하면서, 제공된 함수를 각 아이템에 적용하여 반환된 결과를 싸그리 모아서 새로운 L 객체를 만들어줌.

Path.unlink 라는 함수가 하는일은 failed 에 포함된 모든 아이템 (Path)에 대하여, 파일을 삭제하는일을 수행함. Path.unlink는 Python의 표준 라이브러리임.

failed.map(Path.unlink)

DataBlock의 생성

다운로드된 이미지는 딱히 Trainining, Validation 으로 구분되어 저장되어 있지 않음

  • 경우에 따라서, 데이터셋이 미리 train/valid 와 같은 디렉토리로 나뉘어져 제공되는 경우가 있음.
  • 이 때는 splitter에 할당되는 객체의 valid_pct값을 지정하지 않으면 됨. 그러면 자동으로 trainvalid라는 이름의 디렉토리를 대상으로 삼음. 즉, 다른 이름의 디렉토리라면, 이 이름을 trainvalid 라는 이름으로 맞춰줘야함.
disney_characters = DataBlock(
                      blocks=(ImageBlock, CategoryBlock),
                      get_items=get_image_files,
                      splitter=RandomSplitter(valid_pct=0.2, seed=42),
                      get_y=parent_label,
                      item_tfms=Resize(128),
                      batch_tfms=aug_transforms(mult=2.0, size=224))
dls = disney_characters.dataloaders(path)
dls.show_batch()
learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fine_tune(4)
Downloading: "https://download.pytorch.org/models/resnet34-333f7ec4.pth" to /root/.cache/torch/hub/checkpoints/resnet34-333f7ec4.pth

epoch train_loss valid_loss error_rate time
0 2.595319 0.974732 0.343195 00:29
epoch train_loss valid_loss error_rate time
0 1.281569 0.564789 0.224852 00:28
1 1.021536 0.332185 0.118343 00:28
2 0.805896 0.215167 0.065089 00:29
3 0.666769 0.199968 0.065089 00:29
learn.fine_tune(4)
epoch train_loss valid_loss error_rate time
0 0.381612 0.194724 0.076923 00:28
epoch train_loss valid_loss error_rate time
0 0.319504 0.154517 0.059172 00:28
1 0.319426 0.114375 0.035503 00:29
2 0.276944 0.107262 0.023669 00:28
3 0.250968 0.114793 0.035503 00:28
cleaner = ImageClassifierCleaner(learn)
cleaner
count = 0
for idx in cleaner.delete(): 
  cleaner.fns[idx].unlink()
  count = count+1

print(count)
4
interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix()
learn.export()
btn_upload = widgets.FileUpload()
btn_upload
img = PILImage.create(btn_upload.data[-1])
out_pl = widgets.Output()
out_pl.clear_output()
with out_pl: display(img.to_thumb(128,128))
out_pl
learn_inf = load_learner('export.pkl')
pred,pred_idx,probs = learn_inf.predict(img)
lbl_pred = widgets.Label()
lbl_pred.value = f'Prediction: {pred}; Probability: {probs[pred_idx]:.04f}'
lbl_pred
def on_click_classify(change):
  img = PILImage.create(btn_upload.data[-1])
  out_pl.clear_output()
  with out_pl: display(img.to_thumb(256,256))
  pred,pred_idx,probs = learn_inf.predict(img)
  lbl_pred.value = f'Prediction: {pred}; Probability: {probs[pred_idx]:.04f}'

btn_run = widgets.Button(description='Classify')
btn_run.on_click(on_click_classify)
VBox([widgets.Label('Select your disney character!'),
btn_upload, btn_run, out_pl, lbl_pred])